#include "spriteManager.h"

#include "simulator.h"
#include "springConstraint.h"
#include "explodingSprite.h"
#include "bullet.h"
#include "orb.h"
#include "item.h"

using std::list;

SpriteManager &SpriteManager::getInstance()
{
	static SpriteManager instance;
	return instance;
}

SpriteManager::~SpriteManager()
{
	for(std::list<Sprite *>::iterator it = objects.begin(); it != objects.end(); ++it) {
		delete (*it);
	}
}

SpriteManager::SpriteManager() :
	objects(),
	puppetControllers(),
	player(NULL),
	boss(NULL)
{
	SpriteManager::init();
}

void SpriteManager::init()
{
	Sprite *s = new Sprite("ground");
	s->setType(TYPE_STATIC);
	s->setVisible(false);
	addObject(s);

	//left side
	s = new Sprite("smallplatform");
	s->setType(TYPE_STATIC);
	s->setPosition(Vector2f(250,325));
	addObject(s);

	s = new Sprite("cube");
	s->setType(TYPE_STATIC);
	s->setPosition(Vector2f(610,100));
	addObject(s);

	s = new Sprite("smallplatform");
	s->setType(TYPE_STATIC);
	s->setPosition(Vector2f(850,325));
	addObject(s);

	//center top
	s = new Sprite("longplatform");
	s->setType(TYPE_STATIC);
	s->setPosition(Vector2f(1425,0));
	addObject(s);

	//center bottom
	s = new Sprite("medplatform");
	s->setType(TYPE_STATIC);
	s->setPosition(Vector2f(1848,565));
	addObject(s);

	//right side
	s = new Sprite("smallplatform");
	s->setType(TYPE_STATIC);
	s->setPosition(Vector2f(3646,325));
	addObject(s);

	s = new Sprite("cube");
	s->setType(TYPE_STATIC);
	s->setPosition(Vector2f(3416,100));
	addObject(s);

	s = new Sprite("smallplatform");
	s->setType(TYPE_STATIC);
	s->setPosition(Vector2f(3046,325));
	addObject(s);

	Item *healthPack = new Item();
	healthPack->setType(TYPE_STATIC);
	healthPack->setPosition(Vector2f(625,40));
	addObject(healthPack);

	healthPack = new Item();
	healthPack->setType(TYPE_STATIC);
	healthPack->setPosition(Vector2f(3431,40));
	addObject(healthPack);

	for(int i = 0; i < 4; ++i) {
		Orb *orb = new Orb();
		orb->setRestitution(0.7);
		addObject(orb);
	}
}

void SpriteManager::restart()
{
	for(std::list<Sprite *>::iterator it = objects.begin(); it != objects.end(); ++it) {
		delete (*it);
	}
	objects.clear();
	SpriteManager::init();
}

std::list<Sprite *> &SpriteManager::getObjectList()
{
	return objects;
}

void SpriteManager::addObject(Sprite *d)
{
	objects.push_back(d);
}

void SpriteManager::removeObject(Sprite *d)
{
	objects.remove(d);
	Simulator::getInstance().removeConstraints(d);
}

Sprite *SpriteManager::findObject(Sprite *d)
{
	for(std::list<Sprite *>::iterator it = objects.begin(); it != objects.end(); ++it) {
		if( *it == d ) {
			return d;
		}
	}
	return NULL;
}

void SpriteManager::addPuppetController(PuppetController *pc)
{
	puppetControllers.push_back(pc);
}

void SpriteManager::removePuppetController(Puppet *p)
{
	std::list<PuppetController *>::iterator it = puppetControllers.begin();
	while( it != puppetControllers.end()) {
		if((*it)->getPuppet1() == p || (*it)->getPuppet2() == p)
			it = puppetControllers.erase(it);
		else
			++it;
	}
}

void SpriteManager::draw()
{
	for(std::list<Sprite *>::iterator it = objects.begin(); it != objects.end(); ++it) {
		(*it)->draw();
	}
}

void SpriteManager::update(Uint32 ticks)
{
	static int num_ticks = 0;
	static int timestep = 1000/60;
	num_ticks += ticks;
	if(num_ticks >= timestep) {
		num_ticks %= timestep;
		Simulator::getInstance().step();
	}

	// Manipulate all the puppets
	for(list<PuppetController *>::iterator i = puppetControllers.begin(); i != puppetControllers.end(); ++i) {
		(*i)->manipulate();
	}

	// Update all the sprites and transition between states
	list<Sprite *>::iterator it = objects.begin();
	while(it != objects.end())
	{
		Sprite *sprite = *it;
		if(sprite->isState(STATE_COLLIDING))
		{
			sprite = new ExplodingSprite(*sprite);
			sprite->setState(STATE_EXPLODING);
			sprite->setType(TYPE_STATIC);
			sprite->setCollidable(false);
			objects.push_front(sprite);
			delete *it;
			it = objects.erase(it);
		}
		else if(sprite->isState(STATE_EXPLODING))
		{
			if(static_cast<ExplodingSprite*>(sprite)->chunkCount() == 0) {
				delete *it;
				it = objects.erase(it);
			} else {
				sprite->update(ticks);
				++it;
			}
		}
		else if(sprite->isState(STATE_LIVING))
		{
			// Cull bullets
			/*if(static_cast<Bullet *>(sprite)->getDistance() >= BULLET_DISTANCE) {
				delete sprite;
				it = objects.erase(it);
			} else {
				sprite->update(ticks);
				++it;
			}*/
			sprite->update(ticks);
			++it;
		}
	}
}

Uint32 setItemRespawnTimerCallback(Uint32 interval, void *param)
{
	SDL_Event user_event;

	user_event.type       = SDL_USEREVENT;
	user_event.user.code  = EVENT_SPAWN_ITEM;
	user_event.user.data1 = param;
	user_event.user.data2 = NULL;
	interval = 0;

	SDL_PushEvent(&user_event);

	return interval;
}

void SpriteManager::addItemPickup(Vector2f &pos)
{
	Item *healthPack = new Item();
	healthPack->setType(TYPE_STATIC);
	healthPack->setPosition(pos);
	addObject(healthPack);
}

bool SpriteManager::onContact(Contact &data)
{
	bool doRespond = true;

	// grappling hook
	Sprite *hook = player->getHook();
	if(data.spriteA == hook || data.spriteB == hook) {
		hook->setType(TYPE_STATIC);
		player->getSprite()->setFriction(1.0);
		Simulator::getInstance().addConstraint(new SpringConstraint(player->getSprite(), hook));
	}

	//player touched health pack
	if(dynamic_cast<Item *>(data.spriteA) && (player->getSprite() == data.spriteB)) {
		data.spriteA->setState(STATE_COLLIDING);
		player->addHealth(25);
		ItemSpawnInfo *info = new ItemSpawnInfo();
		info->pos = data.spriteA->getPosition();
		SDLSound::getInstance()[2];
		SDL_AddTimer(10000, setItemRespawnTimerCallback, info);
	} else if((player->getSprite() == data.spriteA) && dynamic_cast<Item *>(data.spriteB)) {
		data.spriteB->setState(STATE_COLLIDING);
		player->addHealth(25);
		ItemSpawnInfo *info = new ItemSpawnInfo();
		info->pos = data.spriteB->getPosition();
		SDLSound::getInstance()[2];
		SDL_AddTimer(10000, setItemRespawnTimerCallback, info);
	}

	// projectile hit something
	Sprite *pSprite = player->getSprite();
	Sprite *bSprite = boss->getSprite();
	if(dynamic_cast<Bullet *>(data.spriteA)) {
		data.spriteA->setState(STATE_COLLIDING);
		// player projectile hit boss
		if(data.spriteA->isOwner(pSprite)) {
			if(data.spriteB == bSprite) {
				player->addScore(20);
				player->shotHit();
				boss->addHealth(-2);
			}
		}
		// boss projectile hits player
		if(data.spriteA->isOwner(bSprite)) {
			if(data.spriteB == pSprite) {
				player->addHealth(-20);
			}
		}
		// projectile hit another projectile
		if(dynamic_cast<Bullet *>(data.spriteB)) {
			data.spriteB->setState(STATE_COLLIDING);
		}
		// player projectile hit orb
		if(dynamic_cast<Orb *>(data.spriteB)) {
			data.spriteB->setState(STATE_COLLIDING);
		}
	} else if(dynamic_cast<Bullet *>(data.spriteB)) {
		data.spriteB->setState(STATE_COLLIDING);
		// player projectile hit boss
		if(data.spriteB->isOwner(pSprite)) {
			if(data.spriteA == bSprite) {
				player->addScore(20);
				player->shotHit();
				boss->addHealth(-2);
			}
		}
		// boss projectile hits player
		if(data.spriteB->isOwner(bSprite)) {
			if(data.spriteA == pSprite) {
				player->addHealth(-20);
			}
		}
		// projectile hit another projectile
		if(dynamic_cast<Bullet *>(data.spriteA)) {
			data.spriteA->setState(STATE_COLLIDING);
		}
		// player projectile hit orb
		if(dynamic_cast<Orb *>(data.spriteA)) {
			data.spriteA->setState(STATE_COLLIDING);
		}
	}

	// player projectile hit a boss projectile
	if(dynamic_cast<Bullet *>(data.spriteA) && dynamic_cast<Bullet *>(data.spriteB)) {
		if(data.spriteA->isOwner(pSprite) && data.spriteB->isOwner(bSprite)) {
			data.spriteB->setState(STATE_LIVING);
			doRespond = false;
		} else if(data.spriteB->isOwner(pSprite) && data.spriteA->isOwner(bSprite)) {
			data.spriteA->setState(STATE_LIVING);
			doRespond = false;
		}
	}

	return doRespond;
}

void SpriteManager::setPlayer(Player *p)
{
	player = p;
}

void SpriteManager::setBoss(Boss *b)
{
	boss = b;
}

Uint32 gameOverCallback(Uint32 interval, void *param)
{
	SDL_Event user_event;

	user_event.type       = SDL_USEREVENT;
	user_event.user.code  = EVENT_GAME_OVER;
	user_event.user.data1 = param;
	user_event.user.data2 = NULL;
	interval = 0;

	SDL_PushEvent(&user_event);

	return interval;
}
